package com.apes.scm.voucher.service.builder;

import com.apes.framework.api.DomainService;
import com.apes.framework.api.SpringManager;
import com.apes.framework.jpa.spec.JpaDsl;
import com.apes.framework.util.EventUtil;
import com.apes.framework.util.MapUtil;
import com.apes.framework.util.ReflectUtil;
import com.apes.framework.util.Arith;
import com.apes.scm.account.account.model.AccountingMethod;
import com.apes.scm.account.account.model.Cooperation;
import com.apes.scm.account.account.model.InventoryBatch;
import com.apes.scm.account.account.model.InventoryMode;
import com.apes.scm.account.account.service.InventoryService;
import com.apes.scm.account.method.Batch;
import com.apes.scm.account.method.BatchMethod;
import com.apes.scm.account.tax.model.Tax;
import com.apes.scm.account.tax.service.TaxService;
import com.apes.scm.contract.model.Contract;
import com.apes.scm.contract.repository.ContractRepository;
import com.apes.scm.masterdata.frame.model.Dept;
import com.apes.scm.masterdata.frame.repository.DeptRepository;
import com.apes.scm.masterdata.party.model.CompanyEntry;
import com.apes.scm.masterdata.party.model.PartyRole;
import com.apes.scm.masterdata.party.repository.CompanyEntryRepository;
import com.apes.scm.masterdata.party.service.SupplierService;
import com.apes.scm.masterdata.product.model.Product;
import com.apes.scm.voucher.model.Voucher;
import com.apes.scm.voucher.model.VoucherInventory;
import com.apes.scm.voucher.model.VoucherItem;
import com.apes.scm.voucher.repository.VoucherInventoryRepository;
import com.apes.scm.voucher.service.BusinessType;
import com.apes.scm.voucher.service.VoucherService;
import com.apes.scm.voucher.service.VoucherType;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static java.lang.Math.abs;
import static java.util.stream.Collectors.groupingBy;

/**
 * 功能：跨公司转储
 *
 * @author xul
 * @create 2019-01-09 17:20
 */
@Component
public class CrossTransferVoucherBuilder extends AbstractVoucherBuilder {
    private static Map<String, Function<Context, Voucher>> routing;

    /**
     * 直营
     */
    public final static String BM_DIRECTLY = "directly";
    /**
     * 子公司
     **/
    public final static String BM_SUBSIDIARY = "subsidiary";
    /**
     * 赋能
     **/
    public final static String BM_EMPOWER = "empower";

    @Autowired
    private VoucherInventoryRepository viRepository;

    @Autowired
    private ContractRepository contractRepository;

    @Autowired
    private SupplierService supplierService;

    @Autowired
    private InventoryService inventoryService;


    public CrossTransferVoucherBuilder() {
        this.voucherType = VoucherType.CROSS_TRANSFER;
        this.BusinessTypes = new String[]{BusinessType.TRANSFER_ORDER};
        routing = initRouting();
    }

    @Override
    public Voucher create(Voucher voucher) {
        if (!voucher.getPartner().getParty().getCorporate().inner()) {
            throw new RuntimeException("调拨对象必须为内部客户！");
        }
        if (voucher.getVoucherItems().stream().filter(voucherItem -> voucherItem.cross()).count() > 0) {
            throw new RuntimeException("调拨不允许做跨公司交易！");
        }
        if (voucher.getVoucherItems().stream().filter(voucherItem -> voucherItem.getInventoryBatch() == null).count() > 0) {
            throw new RuntimeException("调拨请指定批次！");
        }
        crossTransfer(voucher);
        return null;
    }

    private void crossTransfer(Voucher voucher) {
        voucher.getVoucherItems().stream()
                .collect(groupingBy(vi -> new SupplierInfo(vi.getInventoryBatch().getSupplier(), vi.getInventoryBatch().getCooperation())))
                .forEach((supplierInfo, viss) -> {
                    Context ctx = new Context(this, voucher, voucher.getCompany(), voucher.getPartner(), supplierInfo, viss);
                    String sourceModel = getModel(voucher.getCompany());
                    String targetModel = getModel(voucher.getPartner());
                    Function<Context, Voucher> f = getFunction(routing, sourceModel, targetModel);
                    f.apply(ctx);
                });
    }

    /**
     * 功能：初始化跨公司调拨路由
     **/
    private Map<String, Function<Context, Voucher>> initRouting() {
        Map<String, Function<Context, Voucher>> routing = new HashMap<>();

        routing.put(getKey(BM_DIRECTLY, BM_DIRECTLY, "直营-> [直营 ->] 直营"), context -> {
            if (context.isReturn()) { /**销售退回**/
                return context.getBuilder().saleReturn(context);
            } else if (context.getSupplierInfo().isInner() && !context.isSelf()) {
                return multiLevelTransfer(routing, context);
            } else {
                /**销售**/
                return context.getBuilder().sale(context);
            }
        });

        routing.put(getKey(BM_DIRECTLY, BM_EMPOWER, "直营 -> [直营 ->] 赋能"), context -> {
            if (context.getSupplierInfo().isInner() && !context.isSelf()) {
                return multiLevelTransfer(routing, context);
            } else {
                String inventoryMode = context.getSupplierInfo().getCooperation().getInventoryMode();
                PartyRole customer = context.getBuilder().getCustomer(context.getPartner());
                PartyRole company = context.getBuilder().getCompany(customer);
                Contract contract = context.getBuilder().getContract(company, context.getSupplierInfo().getSupplier());
                if (contract != null && InventoryMode.IM_CONSIGN.equals(inventoryMode)) { /**调拨（寄售 + 合同）**/
                    if (context.getSupplierInfo().isInner()) {
                        throw new RuntimeException("直营公司库存异常！") ;
                    }
                    context.getBuilder().innerTransfer(context);
                } else { /**销售（自有或无合同）**/
                    context.getBuilder().sale(context);
                }
                return null;
            }
        });

        routing.put(getKey(BM_DIRECTLY, BM_SUBSIDIARY, "直营-> [直营 ->] 子公司"), context -> {
            if (context.getSupplierInfo().isInner() && !context.isSelf()) {
                return multiLevelTransfer(routing, context);
            } else {
                /**销售**/
                return context.getBuilder().sale(context);
            }
        });

        routing.put(getKey(BM_SUBSIDIARY, BM_DIRECTLY, "子公司-> [直营 ->] 直营"), context -> {
            /**销售退回**/
            if (context.isReturn()) { /**销售退回**/
                return context.getBuilder().saleReturn(context);
            } else if (context.getSupplierInfo().isInner() && !context.isSelf()) {
                return multiLevelTransfer(routing, context);
            } else {
                throw new RuntimeException("不支持跨公司调拨业务！客户公司编码：" + context.getPartner().getId() + ", 供应商: " + context.getSupplierInfo().getSupplier().getId());
            }
        });

        routing.put(getKey(BM_EMPOWER, BM_DIRECTLY, "赋能-> [直营 ->] 直营"), context -> {
            String inventoryMode = context.getSupplierInfo().getCooperation().getInventoryMode();
            PartyRole customer = context.getBuilder().getCustomer(context.getPartner());
            if (context.isReturn()) { /**销售退回**/
                return context.getBuilder().saleReturn(context);
            } else if (context.getSupplierInfo().isInner() && !context.isSelf()) {
                return multiLevelTransfer(routing, context);
            } else if (InventoryMode.IM_CONSIGN.equals(inventoryMode)) { /**调拨（寄售 + 合同）**/
                PartyRole company = context.getBuilder().getCompany(customer);
                Contract contract = context.getBuilder().getContract(company, context.getSupplierInfo().getSupplier());
                if (contract == null) {
                    throw new RuntimeException("不支持跨公司调拨业务！无合同, 公司: " + company.getId() + ", 供应商: " + context.getSupplierInfo().getSupplier().getId());
                }
                return context.getBuilder().innerTransfer(context);
            } else {
                throw new RuntimeException("不支持跨公司调拨业务！客户编码: " + customer.getId() + ", 供应商: " + context.getSupplierInfo().getSupplier().getId() + ", 库存方式: " + inventoryMode);
            }
        });

        routing.put(getKey(BM_EMPOWER, BM_EMPOWER, "赋能 -> [直营 ->] 赋能"), context -> {
            if (context.getSupplierInfo().isInner()) {
                if (context.isReturn()) { /**销售退回**/
                    return context.getBuilder().saleReturn(context);
                } else {
                    return multiLevelTransfer(routing, context);
                }
            } else {
                /**调拨**/
                return context.getBuilder().innerTransfer(context);
            }
        });

        routing.put(getKey(BM_SUBSIDIARY, BM_EMPOWER, "子公司 -> 直营 -> 赋能"), context -> {
            /**销售退回 -> 调拨 | 销售**/
            return multiLevelTransfer(routing, context);
        });

        routing.put(getKey(BM_SUBSIDIARY, BM_SUBSIDIARY, "子公司 -> 直营 -> 子公司"), context -> {
            /**销售退回 -> 销售**/
            return multiLevelTransfer(routing, context);
        });

        routing.put(getKey(BM_EMPOWER, BM_SUBSIDIARY, "赋能 -> 直营 -> 子公司"), context -> {
            PartyRole directly = context.getBuilder().getParentCompany(context.getPartner());
            context.setDirectly(directly);
            return multiLevelTransfer(routing, context);
        });

        return routing;
    }

    /**
     * 功能：调拨（返厂 + 采购）
     **/
    private Voucher innerTransfer(Context context) {
        if (context.getSupplierInfo() == null) {
            throw new RuntimeException("调拨供应商信息不允许为空！");
        }
        /**返厂**/
        Voucher rpv = purchaseReturn(context.getOriginVoucher(), context.getVoucherItems(), context.getCompany(),
                context.getSupplierInfo().getSupplier(), context.getSupplierInfo().getCooperation());

        /**采购**/
        PartyRole company = getCompany(context.getPartner());
        Dept receiptDept = null;
        if (receiptDept == null) receiptDept = getDept(company, "000006");
        if (receiptDept == null) throw new RuntimeException("公司：'" + company.getName() + "' 没有定义采购部门！");
        Contract contract = getContract(company, context.getSupplierInfo().getSupplier());
        boolean companyRoleFlag = company.getParty().isRole("Warehousing");
        if (!companyRoleFlag && Objects.isNull(contract)) throw new RuntimeException(String.format("请先检查是否有合同! 公司: %s, 供应商: %s ", company.getId(), context.getSupplierInfo().getSupplier().getId()));
        String inventoryMode = companyRoleFlag ? InventoryMode.IM_CONSIGN : contract.getCooperation().getInventoryMode();
        return purchase(rpv, company, context.getSupplierInfo().getSupplier(), inventoryMode, receiptDept);
    }

    /**
     * 功能：公司间销售
     **/
    private Voucher sale(Context context) {
        if (context.getCompany().getParty().isRole("Warehousing")) {
            throw new RuntimeException("仓储公司不允许做销售！");
        }
        PartyRole customer = getCustomer(context.getPartner());
        context.getVoucherItems().stream().collect(groupingBy(vi -> getInventoryMode(vi.getInventoryBatch().getCooperation(), context.getCompany(), customer, vi.getProduct().getId())))
                .forEach((inventoryMode, vis) -> {
                    /**销售**/
                    Dept sourceDept = null;
                    if (sourceDept == null) {
                        sourceDept = getDept(context.getCompany(), "000007");
                    }
                    if (sourceDept == null) {
                        throw new RuntimeException("公司：'" + context.getCompany().getName() + "' 没有定义销售部门！");
                    }
                    Voucher sv = sale(context.getOriginVoucher(), vis, context.getCompany(), customer, inventoryMode, sourceDept);

                    /**采购**/
                    PartyRole company = getCompany(context.getPartner());
                    Dept receiptDept = null;
                    if (receiptDept == null) receiptDept = getDept(company, "000006");
                    if (receiptDept == null) {
                        throw new RuntimeException("公司：'" + company.getName() + "' 没有定义采购部门！");
                    }
                    purchase(sv, company, getSupplier(context.getCompany()), sv.getInventoryMode(), receiptDept);
                });
        return null;
    }

    /**
     * 功能：公司间销售退回
     **/
    private Voucher saleReturn(Context context) {
        if (context.getSupplierInfo() == null) {
            throw new RuntimeException("调拨供应商信息不允许为空！");
        }
        Voucher rpv = purchaseReturn(context.getOriginVoucher(), context.getVoucherItems(), context.getCompany(),
                context.getSupplierInfo().getSupplier(), context.getSupplierInfo().getCooperation());
        return saleReturn(rpv, getCompany(context.getSupplierInfo().getSupplier()), getCustomer(context.getCompany()));
    }

    /**
     * 功能：多级调拨
     **/
    private Voucher multiLevelTransfer(Map<String, Function<Context, Voucher>> routing, Context context) {
        /**子公司 -> 直营 | 赋能 -> 直营 ｜ 直营 -> 直营**/
        PartyRole partner = context.getDirectly() != null ? context.getDirectly() : context.getSupplierInfo().getSupplier();
        Context ctx = new Context(context.getBuilder(), context.getOriginVoucher(), context.getCompany(),
                partner, context.getSupplierInfo(), context.getVoucherItems());
        String sourceModel = context.getBuilder().getModel(context.getCompany());
        String targetModel = context.getBuilder().getModel(partner);
        Function<Context, Voucher> f = getFunction(routing, sourceModel, targetModel);
        Voucher voucher;
        try {
            voucher = f.apply(ctx);
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }

        /**直营 -> 子公司 ｜ 直营 -> 赋能 ｜ 直营 -> 直营 **/
        PartyRole company = context.getBuilder().getCompany(partner);
        Set<VoucherItem> voucherItems = new HashSet<>();
        voucher.getVoucherItems().stream().forEach(vItem -> new BatchMethod<Batch>() {
            @Override
            public Iterable<Batch> wrap() {
                List<InventoryBatch> inventories = context.getBuilder().inventoryService.findInventoryBatchForSaleOrder(company, null, vItem.getProduct());
                return context.getBuilder().inventoryService.wrapBatch(inventories, vItem.getUom(), inventory -> inventory.getBalanceQty() - inventory.getFrostQty());
            }

            @Override
            public void succeed(Batch batch, double increment) {
                VoucherItem vi = new VoucherItem();
                vi.setProduct(vItem.getProduct());
                vi.setUom(vItem.getUom());
                InventoryBatch inventoryBatch = (InventoryBatch) batch.getEntity();
                vi.setInventoryBatch(inventoryBatch);
                vi.setPriceUnit(vItem.getPriceUnit());
                vi.setProductQty(increment);
                vi.setOriginItemId(vItem.getOriginItemId());
                vi.setSourceItemId(String.valueOf(vItem.getId()));
                voucherItems.add(vi);

                /**冻结批次**/
                context.getBuilder().inventoryService.changeFrostQty(inventoryBatch, vItem.getUom(), abs(increment), company,
                        voucher.getInnerBusinessType(), voucher.getId(), String.valueOf(vItem.getId()), "调拨库存冻结批次");
            }

            @Override
            public void error(double surplus) {
                throw new RuntimeException("商品：[" + vItem.getProduct().getId() + "]" + vItem.getProduct().getName() + " 价值库存数量少 " + surplus + vItem.getUom().getName() + "！");
            }
        }.execute(abs(vItem.getProductQty())));

        voucherItems.stream()
                .collect(groupingBy(vi -> new SupplierInfo(vi.getInventoryBatch().getSupplier(), vi.getInventoryBatch().getCooperation())))
                .forEach((supplierInfo, viss) -> {
                    Context ctx1 = new Context(context.getBuilder(), context.getOriginVoucher(), company, context.getPartner(), supplierInfo, viss);
                    String sourceModel1 = context.getBuilder().getModel(company);
                    String targetModel1 = context.getBuilder().getModel(context.getPartner());
                    Function<Context, Voucher> f1 = getFunction(routing, sourceModel1, targetModel1);
                    f1.apply(ctx1);
                });
        return null;
    }


    /**
     * 内部销售
     */
    private Voucher sale(Voucher voucher, List<VoucherItem> voucherItems, PartyRole company, PartyRole customer, String inventoryMode, Dept dept) {
        Voucher sv = new Voucher();
        sv.setScene(voucher.getScene());
        sv.setCustomerExpand(voucher.getCustomerExpand());
        sv.setVoucherType(VoucherType.CUSTOMER_SALE);
        sv.setBusinessType(BusinessType.SALE_ORDER);
        sv.setCompany(company);
        sv.setPartner(customer);
        ReflectUtil.copy(voucher, sv, "scene", "originBusinessType", "origin");
        sv.setSourceBusinessType(voucher.getSourceBusinessType());
        sv.setSource(voucher.getSource());
        sv.setInvoiced(true);
        sv.setReturned(false);
        sv.setInventoryMode(inventoryMode);
        sv.setAccountingMethod(AccountingMethod.AM_PURCHASE);
        sv.setDept(dept);

        Set<VoucherItem> vis = new HashSet<>();
        voucherItems.stream().forEach((vItem) -> {
            VoucherItem item = new VoucherItem();
            item.setVoucher(sv);
            item.setProduct(vItem.getProduct());
            item.setUom(vItem.getUom());
            InventoryBatch inventoryBatch = vItem.getInventoryBatch();
            double price = inventoryBatch.getUom().computePrice(inventoryBatch.getTaxCostPrice(), vItem.getUom());
            if (inventoryBatch.getUom().getId() == vItem.getUom().getId() && price != inventoryBatch.getTaxCostPrice()) {
                throw new RuntimeException("商品：[" + vItem.getProduct().getId() + "]" + vItem.getProduct().getName() + "调拨销售价格异常");
            }
            price = raisePrice(company, customer, inventoryBatch.getSupplier(), vItem.getProduct(), price);
            item.setPriceUnit(price);
            item.setProductQty(abs(vItem.getProductQty()));
            item.setInventoryBatch(inventoryBatch);
            item.setTax(SpringManager.getBean(TaxService.class).getSaleTax(company, item.getProduct()));
            item.setOriginItemId(vItem.getOriginItemId());
            item.setSourceItemId(String.valueOf(vItem.getId()));
            vis.add(item);
        });
        sv.setVoucherItems(vis);
        return SpringManager.getBean(VoucherService.class).save(sv);
    }

    /**
     * 加价
     **/
    private double raisePrice(PartyRole company, PartyRole customer, PartyRole supplier, Product product, double taxCostPrice) {
        return new DomainService().invoke("cross.company.transferNew.getS950", MapUtil.mapper(
                "companyId", company.getId(),
                "customerId", customer.getId(),
                "supplierId", supplier.getId(),
                "productId", product.getId(),
                "taxCostPrice", taxCostPrice));
    }

    private double calcPurchasePrice(PartyRole originCompany, PartyRole company, PartyRole supplier, Product product, double originPrice) {
        if (supplier.getParty().getCorporate().inner()) {
            return originPrice;
        }
        double originRate = getCompanyRate(originCompany, supplier, product);
        double price = Arith.div(originPrice, 1 + originRate / 100, 4);
        double rate = getCompanyRate(company, supplier, product);
        price = Arith.round(Arith.mul(price, 1 + rate / 100), 4);
        return price;
    }

    private double getCompanyRate(PartyRole company, PartyRole supplier, Product product) {
        double rate = 0;
        boolean isWantInvoice = true;
        CompanyEntry companyEntry = SpringManager.getBean(CompanyEntryRepository.class).findByCode(company.getId());
        if (companyEntry != null) {
            isWantInvoice = companyEntry.isWantInvoice();
        }
        if (isWantInvoice) {
            rate = supplierService.getSupplierTicketPoint(company.getId(), supplier.getId(), product.getId()); //税点
        }
        return rate;
    }

    /**
     * 内部采购
     */
    private Voucher purchase(Voucher sv, PartyRole company, PartyRole supplier, String inventoryMode, Dept dept) {
        Voucher rpv = new Voucher();
        rpv.setScene(sv.getScene());
        rpv.setCustomerExpand(sv.getCustomerExpand());
        rpv.setVoucherType(VoucherType.SUPPLIER_PURCHASE);
        rpv.setBusinessType(BusinessType.PURCHASE_ORDER);
        rpv.setCompany(company);
        rpv.setPartner(supplier);
        ReflectUtil.copy(sv, rpv, "scene", "originBusinessType", "origin");
        rpv.setSourceBusinessType(sv.getSourceBusinessType());
        rpv.setSource(sv.getSource());
        rpv.setInvoiced(true);
        rpv.setReturned(false);
        rpv.setInventoryMode(inventoryMode);
        rpv.setAccountingMethod(AccountingMethod.AM_PURCHASE);
        rpv.setDept(dept);
        rpv.setVoucherItems(sv.getVoucherItems().stream()
                .map(item -> {
                    VoucherItem voucherItem = new VoucherItem();
                    voucherItem.setVoucher(rpv);
                    voucherItem.setProduct(item.getProduct());
                    voucherItem.setUom(item.getUom());
                    double price = calcPurchasePrice(sv.getCompany(), company, supplier, item.getProduct(), item.getPriceUnit());
                    voucherItem.setPriceUnit(price);
                    voucherItem.setProductQty(abs(item.getProductQty()));
                    voucherItem.setTax(SpringManager.getBean(TaxService.class).getPurchaseTax(company, supplier, item.getProduct()));
                    voucherItem.setOriginItemId(item.getOriginItemId());
                    voucherItem.setSourceItemId(String.valueOf(item.getId()));
                    return voucherItem;
                }).collect(Collectors.toSet()));
        return SpringManager.getBean(VoucherService.class).save(rpv);
    }

    /**
     * 内部采购退货
     */
    private Voucher purchaseReturn(Voucher voucher, List<VoucherItem> voucherItems, PartyRole company, PartyRole supplier, Cooperation cooperation) {
        Voucher rpv = new Voucher();
        rpv.setScene(voucher.getScene());
        rpv.setCustomerExpand(voucher.getCustomerExpand());
        rpv.setVoucherType(VoucherType.SUPPLIER_PURCHASE);
        rpv.setBusinessType(BusinessType.PURCHASE_RETURN);
        rpv.setCompany(company);
        rpv.setPartner(supplier);
        ReflectUtil.copy(voucher, rpv, "scene", "originBusinessType", "origin");
        rpv.setSourceBusinessType(voucher.getSourceBusinessType());
        rpv.setSource(voucher.getSource());
        rpv.setInvoiced(true);
        rpv.setReturned(true);
        rpv.setInventoryMode(cooperation.getInventoryMode());
        rpv.setAccountingMethod(cooperation.getAccountingMethod());
        rpv.setDept(voucherItems.stream().findFirst().orElse(null).getInventoryBatch().getDept());

        Set<VoucherItem> returnItems = new HashSet<>();
        voucherItems.stream().forEach((vItem) -> {
            VoucherItem item = new VoucherItem();
            item.setVoucher(rpv);
            item.setProduct(vItem.getProduct());
            item.setUom(vItem.getUom());
            InventoryBatch inventoryBatch = vItem.getInventoryBatch();
            double price = inventoryBatch.getUom().computePrice(inventoryBatch.getTaxCostPrice(), vItem.getUom());
            item.setPriceUnit(price);
            item.setProductQty(-1 * abs(vItem.getProductQty()));
            item.setInventoryBatch(inventoryBatch);
            item.setTax(inventoryBatch.getTax());
            item.setOriginItemId(vItem.getOriginItemId());
            item.setSourceItemId(String.valueOf(vItem.getId()));
            item.setOriginalSupplier(vItem.getOriginalSupplier());
            returnItems.add(item);
        });

        rpv.setVoucherItems(returnItems);
        return SpringManager.getBean(VoucherService.class).save(rpv);
    }

    /**
     * 内部销售退货
     */
    private Voucher saleReturn(final Voucher rpv, PartyRole company, PartyRole customer) {
        Voucher rsv = new Voucher();
        rsv.setScene(rpv.getScene());
        rsv.setCustomerExpand(rpv.getCustomerExpand());
        rsv.setVoucherType(VoucherType.CUSTOMER_SALE);
        rsv.setBusinessType(BusinessType.SALE_RETURN);
        rsv.setCompany(company);
        rsv.setPartner(customer);
        ReflectUtil.copy(rpv, rsv, "scene", "originBusinessType", "origin");
        rsv.setSourceBusinessType(rpv.getSourceBusinessType());
        rsv.setSource(rpv.getSource());
        rsv.setInvoiced(true);
        rsv.setReturned(true);
        rsv.setInventoryMode(rpv.getInventoryMode());
        rsv.setAccountingMethod(rpv.getAccountingMethod());

        Set<VoucherItem> vis = new HashSet<>();
        if (rsv.getInventoryMode().equals(InventoryMode.IM_OWN)) {
            rpv.getVoucherItems().stream().forEach(item -> new BatchMethod<Batch>() {
                @Override
                public Iterable<Batch> wrap() {
                    List<Batch> batches = new ArrayList<>();
                    double total = abs(item.getProductQty());
                    List<VoucherInventory> voucherInventories;
                    if (item.getOriginalSupplier() != null) {
                        voucherInventories = viRepository.findInventoryBatchForSaleReturn(company, customer, item.getOriginalSupplier(), item.getProduct(), PageRequest.of(0, 100)).getContent();
                    } else {
                        voucherInventories = viRepository.findInventoryBatchForSaleReturn(company, customer, item.getProduct(), PageRequest.of(0, 100)).getContent();
                    }
                    if (voucherInventories != null && !voucherInventories.isEmpty()) {
                        for (VoucherInventory vib : voucherInventories) {
                            Batch batch = new Batch();
                            batch.setEntity(vib);
                            double returnableQty = vib.getProductQty() - vib.getQtyReturned() - vib.getFrostQty();
                            if (returnableQty == 0) continue;
                            double qty = vib.getUom().computeQty(returnableQty, item.getUom(), true);
                            batch.setQty(qty);
                            batches.add(batch);
                            total -= qty;
                        }
                    }
                    if (total > 0) {
                        Map m = new HashMap();
                        if (rsv.getDept() == null) {
                            m.put("dept", getDept(rsv.getCompany(), "000007"));
                        } else {
                            m.put("dept", rsv.getDept());
                        }
                        m.put("tax", SpringManager.getBean(TaxService.class).getSaleTax(rsv.getCompany(), item.getProduct()));
                        Batch batch = new Batch();
                        batch.setEntity(m);
                        batch.setQty(total);
                        batches.add(batch);
                    }
                    return batches;
                }

                @Override
                public void succeed(Batch batch, double increment) {
                    Tax tax = null;
                    Dept dept = null;
                    VoucherItem voucherItem = null;
                    if (batch.getEntity() instanceof VoucherInventory) {
                        VoucherInventory vib = (VoucherInventory) batch.getEntity();
                        voucherItem = vib.getVoucherItem();
                        tax = vib.getTax();
                        dept = vib.getDept();
                        double qty = item.getUom().computeQty(increment, vib.getUom(), true);
                        vib.changeFrostQty(qty);
                    } else if (batch.getEntity() instanceof Map) {
                        Map m = (Map) batch.getEntity();
                        tax = (Tax) m.get("tax");
                        dept = (Dept) m.get("dept");
                    }

                    VoucherItem vi = new VoucherItem();
                    vi.setVoucher(rsv);
                    if (rsv.getDept() == null) {
                        rsv.setDept(dept);
                    }
                    vi.setProduct(item.getProduct());
                    vi.setTax(tax);
                    vi.setUom(item.getUom());
                    vi.setPriceUnit(item.getPriceUnit());
                    vi.setProductQty(-1 * abs(increment));
                    vi.setOriginItemId(item.getOriginItemId());
                    vi.setSourceItemId(item.getSourceItemId());
                    vi.setVoucherItem(voucherItem);
                    vis.add(vi);
                }

                @Override
                public void error(double surplus) {
                    throw new RuntimeException("商品：[" + item.getProduct().getId() + "]" + item.getProduct().getName() + " 价值库存数量少 " + surplus + item.getUom().getName() + "！");
                }
            }.execute(abs(item.getProductQty())));
        } else {
            rpv.getVoucherItems().stream().forEach(item -> {
                VoucherItem vi = new VoucherItem();
                vi.setVoucher(rsv);
                if (rsv.getDept() == null) {
                    rsv.setDept(getDept(rsv.getCompany(), "000007"));
                }
                vi.setProduct(item.getProduct());
                vi.setTax(SpringManager.getBean(TaxService.class).getSaleTax(rsv.getCompany(), item.getProduct()));
                vi.setUom(item.getUom());
                vi.setPriceUnit(item.getPriceUnit());
                vi.setProductQty(-1 * abs(item.getProductQty()));
                vi.setOriginItemId(item.getOriginItemId());
                vi.setSourceItemId(item.getSourceItemId());
                vis.add(vi);
            });
            if (rsv.getDept() == null) {
                Dept dept = getDept(rsv.getCompany(), "000007");
                if (dept == null) throw new RuntimeException("公司：'" + rsv.getCompany().getName() + "' 没有定义销售部门！");
                rsv.setDept(dept);
            }
        }
        rsv.setVoucherItems(vis);
        return SpringManager.getBean(VoucherService.class).save(rsv);
    }

    private Contract getContract(PartyRole company, PartyRole supplier) {
        String platformCompanyId = getPlatformCompanyId(company);
        List<Contract> contracts;
        if (!StringUtils.isEmpty(platformCompanyId)) {
            contracts = contractRepository.qryValidContract(platformCompanyId, "Contract.contractType.platform", supplier.getId());
            if (!contracts.isEmpty()) {
                return contracts.stream().findFirst().get();
            }
        }
        contracts = contractRepository.qryValidContract(company.getId(), "Contract.contractType.supplier", supplier.getId());
        if (!contracts.isEmpty()) {
            return contracts.stream().findFirst().get();
        }
        return null;
    }

    private Dept getDept(PartyRole company, String type) {
        Dept dept;
        List<Dept> depts = SpringManager.getBean(DeptRepository.class).findAll(JpaDsl.toCriteriaByEq(
                "company", company, "deptType.id", type, "valid", true)).stream().collect(Collectors.toList());
        if (depts.size() == 1) {
            dept = depts.stream().findFirst().orElse(null);
        } else {
            dept = depts.stream().filter(Dept::isInnerTag).findFirst().orElse(null);
        }

        if (dept == null && "000007".equals(type)) {
            depts = SpringManager.getBean(DeptRepository.class).findAll(JpaDsl.toCriteriaByEq(
                    "company", company, "deptType.id", type)).stream().collect(Collectors.toList());
            dept = depts.stream().filter(Dept::isInnerTag).findFirst().orElse(null);
        }
        return dept;
    }

    private String getInventoryMode(Cooperation cooperation, PartyRole company, PartyRole customer, String productId) {
        if (customer.getParty().isRole("Warehousing")) {
            return InventoryMode.IM_CONSIGN;
        }
        if (InventoryMode.IM_CONSIGN.equals(cooperation.getInventoryMode())) {
            Boolean isImOwer = new DomainService().invoke("cm.PurchaseSellRange.check", MapUtil.mapper("companyId", company.getId(), "customerId", customer.getId(), "productId", productId));
            return isImOwer ? InventoryMode.IM_OWN : InventoryMode.IM_CONSIGN;
        } else if (InventoryMode.IM_OWN.equals(cooperation.getInventoryMode())) {
            Boolean isImConsign = new DomainService().invoke("cm.ReplacementRange.check", MapUtil.mapper("companyId", company.getId(), "customerId", customer.getId(), "productId", productId));
            return isImConsign ? InventoryMode.IM_CONSIGN : InventoryMode.IM_OWN;
        }
        return cooperation.getInventoryMode();
    }

    private Function<Context, Voucher> getFunction(Map<String, Function<Context, Voucher>> routing, String sourceModel, String targetModel) {
        String key = getKey(sourceModel, targetModel, "");
        return getFunction(routing, key);
    }

    private Function getFunction(Map<String, Function<Context, Voucher>> routing, String key) {
        Function f = routing.get(key);
        if (f == null) throw new RuntimeException("不支持跨公司调拨业务！");
        return f;
    }

    /**
     * 功能：查询公司运营模式
     **/
    public String getModel(PartyRole partyRole) {
        String model = getCompanyModel(partyRole);
        if (model == null) {
            throw new RuntimeException(String.format("公司：%s[%s]运营模式异常！", partyRole.getName(), partyRole.getId()));
        }
        return model;
    }

    public String getCompanyModel(PartyRole partyRole) {
        PartyRole company = getCompany(partyRole);
        String platformCompanyId = getPlatformCompanyId(company);
        if (!StringUtils.isEmpty(platformCompanyId)) {
            return BM_EMPOWER;
        }
        PartyRole parent = getParentCompany(company);
        if (parent != null) {
            return BM_SUBSIDIARY;
        }
        if (isDirectlyCompany(company)) {
            return BM_DIRECTLY;
        }
        return null;
    }

    /**
     * 功能：查询上级公司（直营）
     **/
    private PartyRole getParentCompany(PartyRole company) {
        return new DomainService().invoke("cm.partyRoleService.getParentCompany", MapUtil.mapper("companyId", company.getId()));
    }

    /**
     * 功能：检查是否直营公司
     **/
    private boolean isDirectlyCompany(PartyRole company) {
        return new DomainService().invoke("cm.businessParam.isDirectlyCompany", MapUtil.mapper("companyId", company.getId()));
    }


    /**
     * 功能：查询平台公司（赋能）
     **/
    private String getPlatformCompanyId(PartyRole company) {
        String topic = "event:fn.contract.findFirstValidFnContractCompanyByPartnerId";
        return (String) EventUtil.sendSynEvent(this, topic, MapUtil.mapper("partnerId", company.getId())).stream().findFirst().get();
    }

    public String getKey(String source, String target, String notes) {
        return source + "#" + target;
    }

    @Data
    class SupplierInfo {
        private PartyRole supplier;
        private Cooperation cooperation;

        public SupplierInfo(PartyRole supplier, Cooperation cooperation) {
            this.supplier = supplier;
            this.cooperation = cooperation;
        }

        public boolean isInner() {
            return supplier.getParty().getCorporate().inner();
        }
    }

    @Data
    class Context {

        private CrossTransferVoucherBuilder builder;
        /**
         * 原始凭证
         **/
        private Voucher originVoucher;
        /**
         * 公司
         **/
        private PartyRole company;
        /**
         * 合作伙伴
         **/
        private PartyRole partner;
        /**
         * 供应商信息
         **/
        private CrossTransferVoucherBuilder.SupplierInfo supplierInfo;
        /**
         * 业务明细
         **/
        private List<VoucherItem> voucherItems;
        /**
         * 直营公司
         **/
        private PartyRole directly;

        public Context(CrossTransferVoucherBuilder builder, Voucher originVoucher, PartyRole company, PartyRole partner,
                       CrossTransferVoucherBuilder.SupplierInfo supplierInfo, List<VoucherItem> voucherItems) {
            this.builder = builder;
            this.originVoucher = originVoucher;
            this.company = company;
            this.partner = partner;
            this.supplierInfo = supplierInfo;
            this.voucherItems = voucherItems;
        }

        public boolean isSelf() {
            return company.getParty().getId() == supplierInfo.getSupplier().getParty().getId();
        }

        public boolean isReturn() {
           return partner.getParty().getId() == supplierInfo.getSupplier().getParty().getId();
        }
    }
}
